﻿using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using UnityEngine;
using DataSystem;
using System.Linq;
using System;
using Utility.Math;
using Utility.Random;
using System.Runtime.Serialization;
using Newtonsoft.Json;
using Utility;
using Utility.DataSystem;

namespace TerrainSystem
{
	public partial class TerrainObject : MonoBehaviour
	{
		//Game Game => Main.Game;

		#region life cycle
		private void Awake ()
		{
			Data = new TerrainData (this);
			InitPathfind ();
			Data.GroundTile_Init ();
		}

		partial void InitPathfind ();


		public void OnEnterGame ()
		{
			Data.OnEnterGame ();
			//Map_Init ();
		}

		public void OnExitGame ()
		{
			Data.OnExitGame ();
		}

		public void OnEnterBattle ()
		{
		}

		public void OnExitBattle ()
		{

		}

		private void Update ()
		{
			WarFogManager.Draw (Data.GroundTileCoords, transform.position);
		}
		#endregion
		#region terrain data
		public TerrainData Data { get; private set; }


		public Vector2Int PositionToCoord (Vector3 worldPosition)
		{
			Vector3 localPos = transform.InverseTransformPoint (worldPosition);
			int coord_x = Mathf.FloorToInt (localPos.x + 0.5f);
			int coord_y = Mathf.FloorToInt (localPos.z + 0.5f);
			return new Vector2Int (coord_x, coord_y);
		}

		public Vector3 CoordToPosition (Vector2Int coord)
		{
			return transform.position + new Vector3 (coord.x, 0, coord.y);
		}

		public Vector3 GetSurfacePosition (Vector2Int coord)
		{
			float h = Data.GetTopTile (coord).Component?.SurfaceHeight ?? 0;
			Vector3 pos = new Vector3 (coord.x, h, coord.y);
			return transform.TransformPoint (pos);
		}
		#endregion
		#region tile info
		//tile
		public bool CanBuild (Vector2Int coord)
		{
			if (Data.GetTopTile (coord).Component?.CanBuild ?? false)
			{
				//return !Data.GetBuilding (coord);
				return true;
			}
			return false;
		}

		public bool IsVoid (Vector2Int coord)
		{
			return !Data.GetTopTile (coord);
		}

		public bool IsGround (Vector2Int coord)
		{
			return Data.GetTopTile (coord).Component?.IsGround ?? false;
		}

		public bool IsPlatform (Vector2Int coord)
		{
			return Data.GetTopTile (coord).Component?.IsPlatform ?? false;
		}

		public bool IsEdge (Vector2Int coord)
		{
			if (!IsVoid (coord))
			{
				return IsVoid (coord + Vector2Int.up) || IsVoid (coord + Vector2Int.down) || IsVoid (coord + Vector2Int.left) || IsVoid (coord + Vector2Int.right);
			}
			return false;
		}

		#endregion
		#region init map
		/*void Map_Init ()
		{
			var difficultySetting = Game.PerDifficultySetting;
			var rnd = Game.Rnd;
			int mapRadius = Game.GameSetting.MapRadius;
			var pool = difficultySetting.MapObjectSettings;

			var map_invalidTiles = BufferSet<Vector2Int>.Spawn ();

			//initial piece
			if (Game.Data.EnableCenterGround)
			{
				var groundSetting = SettingCenter.GetSetting<TileGroupSetting> (Game.Context.GetValue (F_Game.Game_CenterGroundTileGroupSettingName).AsString ());
				//var groundSetting = Game.Rnd.GetElement (difficultySetting.TilegroupSettings_ByCategory[TileGroupSetting.Categorys.Ground]);
				if (groundSetting != null)
				{
					var ground = TileGroup.Spawn (groundSetting);
					Data.AddTileGroup (Vector2Int.zero, ground, false);
					MarkInvalidTiles (ground, Game.Data.CenterGroundMargin);
					Data.OpenFog (ground, Game.GameSetting.MapVisualRange);
				}
			}

			//init pool
			if (Game.Data.EnableRandomMapObjects)
			{
				var mapObjectPool = BufferDict<MapObjectSetting, int>.Spawn ();
				var mapObj_posPool = BufferList<MapObjectSetting>.Spawn ();//include 0
				var mapObj_negPool = BufferList<MapObjectSetting>.Spawn ();

				foreach (var tg in pool)
				{
					mapObjectPool.Add (tg, 0);
					if (tg.DifficultyScore >= 0)
					{
						mapObj_posPool.Add (tg);
					}
					if (tg.DifficultyScore < 0)
					{
						mapObj_negPool.Add (tg);
					}
				}

				//min num
				foreach (var tg in pool)
				{
					if (tg.MinCount.HasValue)
					{
						for (int i = 0; i < tg.MinCount.Value; i++)
						{
							Map_TryPlaceTerrainPiece (tg, 30);
						}
					}
				}

				//random pieces
				float score = Game.Data.MapDifficultyScore;
				for (int i = 0; i < Game.GameSetting.MapRandomTilegroupMaxCount; i++)
				{
					var setting = rnd.GetWeightedElement ((score <= 0 ? mapObj_posPool : mapObj_negPool), GetWeight);
					if (Map_TryPlaceTerrainPiece (setting, 3))
					{
						score += setting.DifficultyScore;
					}
				}

				mapObjectPool.Dispose ();
				mapObj_posPool.Dispose ();
				mapObj_negPool.Dispose ();

				bool Map_TryPlaceTerrainPiece (MapObjectSetting setting, int chances = 1)
				{
					if (setting == null)
					{
						return false;
					}

					bool success = false;

					var tg = TileGroup.Spawn (setting.TileGroupSetting);
					int margin = setting.MapMargin;
					for (int i = 0; i < chances; i++)
					{
						if (TryPlaceTileGroup ())
						{
							success = true;
							break;
						}
					}
					//check max count
					if (success)
					{
						mapObjectPool[setting]++;
						if (setting.MaxCount.HasValue)
						{
							if (mapObjectPool[setting] >= setting.MaxCount.Value)
							{
								(setting.DifficultyScore >= 0 ? mapObj_posPool : mapObj_negPool).Remove (setting);
							}
						}
					}
					else
					{
						tg.Despawn ();
					}
					return success;

					//will never land at (0, 0)
					Vector2Int RndCoord ()
					{
						var t = rnd.LinearWeightedFloat (setting.PositionWeight, setting.PositionRange).Clamp (0, 1);
						int r = Mathf.CeilToInt (t * mapRadius * 2);
						int x = rnd.GetInt (0, r);
						int y = r - x;

						return new Vector2Int (rnd.GetSign () * x, rnd.GetSign () * y);
					}

					bool TryPlaceTileGroup ()
					{
						tg.Component.Orientation = (Rotation4)Game.Rnd.GetInt (0, 3);
						Vector2Int coord = RndCoord ();
						TileGroup.SetTerrainObject (tg, null, coord);

						if (CanPlaceTileGroup (tg, margin))
						{
							Data.AddTileGroup (coord, tg, false);
							MarkInvalidTiles (tg, margin);
							return true;
						}
						return false;
					}
				}

			}

			//clear
			map_invalidTiles.Dispose ();

			float GetWeight (MapObjectSetting setting)
			{
				return setting.Weight;
			}

			void MarkInvalidTiles (TileGroup tilegroup, int margin)
			{
				using (var buffer = BufferSet<Vector2Int>.Spawn ())
				{
					foreach (var tileRef in tilegroup.Tiles)
					{
						if (tileRef)
						{
							var coord = tileRef.Component.Coord;
							FillBuffer_Region (buffer, coord, margin);
						}
					}
					map_invalidTiles.AddRange (buffer);
				}
			}

			bool CanPlaceTileGroup (TileGroup tileGroup, int margin)
			{
				using (var buffer = BufferSet<Vector2Int>.Spawn ())
				{
					//check exist invalid tiles
					foreach (var tileRef in tileGroup.Tiles)
					{
						var tileCoord = tileRef.Component.Coord;
						FillBuffer_Region (buffer, tileCoord, margin);
						if (map_invalidTiles.Contains (tileCoord))
						{
							return false;
						}
					}
					//check tg margin
					foreach (var coord in buffer)
					{
						//map radius
						if (!Data.IsInMap (coord))
						{
							return false;
						}
						//exist tile
						if (Data.GetTopTile (coord))
						{
							return false;
						}
					}
				}
				return true;
			}

			void FillBuffer_Region (HashSet<Vector2Int> set, Vector2Int coord, int margin)
			{
				for (int dx = -margin; dx <= margin; dx++)
				{
					for (int dy = -margin; dy <= margin; dy++)
					{
						if (Mathf.Abs (dx) + Mathf.Abs (dy) <= margin)
						{
							set.Add (coord + new Vector2Int (dx, dy));
						}
					}
				}
			}
		}*/
		#endregion

		#region serilzation
		public void LoadData (TerrainData data)
		{
			Data = data;

		}
		#endregion
	}
}